package com.vx.ratingTool.services;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.vx.ratingTool.entities.Match;
import com.vx.ratingTool.entities.MatchFinder;
import com.vx.ratingTool.entities.Player;

@Service
public class MatchService implements IMatchService {

	@Autowired
	private IPlayerBaseManager playerBaseManager;
	private Match match;
	private MatchFinder MF;
	private List<Player> foundPlayers;

	public MatchService(IPlayerBaseManager playerBaseManager) {
		super();
		this.playerBaseManager = playerBaseManager;
	}

	public MatchService() {
		super();
	}

	@Override
	public void createMatchFinder(List<Player> playerPool, Player searchingPlayer, int searchRangeBase, int searchRangeIncrement) {
		setMF(new MatchFinder(playerPool, searchingPlayer, searchRangeBase, searchRangeIncrement));
	}

	@Override
	public List<Player> getFoundPlayers() {
		List<Player> toaddList = new ArrayList();
		for (int i = 0; i < foundPlayers.size(); i++) {
			toaddList.add(new Player(foundPlayers.get(i)));
		}
		return toaddList;
	}

	private Match findMatch() {
		Match newMatch;

		foundPlayers = new ArrayList<Player>();
		foundPlayers.add(getMF().getSearchingPlayer());
		// ////////////////////////////////////////////////////////////////////////////////
		// System.out.println(foundPlayers.get(0).getName() +
		// " ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||");

		// search for players with this search range, if not found increase it
		while (getFoundPlayers().size() < 10) {
			for (int i = 0; i < getMF().getPlayerPool().size(); i++) {

				Player nextPlayer = getMF().getPlayerPool().get(i);

				if (nextPlayer.getId() != getMF().searchingPlayer.getId() & !playerIsFoundAlready(foundPlayers, nextPlayer.getId())) {

					// checking if next player in the pool is in rating range
					if (nextPlayer.getRating() >= getMF().searchingPlayer.getRating() - getMF().getSearchRange()
							& nextPlayer.getRating() <= getMF().searchingPlayer.getRating() + getMF().getSearchRange()) {
						foundPlayers.add(nextPlayer);
						if (foundPlayers.size() == 10)
							break;
					}
				}
			}
			getMF().setSearchRange(getMF().getSearchRange() + getMF().getSearchRangeIncrement());
		}

		// create equal teams from found players
		int ratingDiff, ratingDiffBest;
		List<Player> currentList, bestList, teamA, teamB;

		currentList = new ArrayList<Player>(foundPlayers);
		ratingDiffBest = calculateRatingDiff(currentList);
		bestList = new ArrayList<Player>(currentList);

		for (int i = 0; i < 5; i++) { // for each player in team 1
			for (int j = 5; j < 10; j++) {// swap with each player of team 2
				currentList = new ArrayList<Player>(foundPlayers);
				currentList.set(i, getFoundPlayers().get(j));
				currentList.set(j, getFoundPlayers().get(i));
				ratingDiff = calculateRatingDiff(currentList); // calc diff
				if (ratingDiff < ratingDiffBest) {
					ratingDiffBest = ratingDiff;
					bestList = new ArrayList<Player>(currentList);
				}
			}
		}

		// create match
		teamA = new ArrayList<Player>();
		teamB = new ArrayList<Player>();

		for (int j = 0; j < 5; j++) {
			teamA.add(bestList.get(j));
			teamB.add(bestList.get(5 + j));
		}
		newMatch = new Match(teamA, teamB);
		return newMatch;
	}

	private boolean playerIsFoundAlready(List<Player> players, int id) {
		for (int i = 0; i < players.size(); i++) {
			if (players.get(i).getId() == id)
				return true;
		}
		return false;
	}

	private int calculateRatingDiff(List<Player> currentList) {
		int ratingA, ratingB;
		ratingA = 0;
		for (int j = 0; j < 5; j++) {
			ratingA += currentList.get(j).getRating();
		}
		ratingB = 0;
		for (int j = 5; j < 10; j++) {
			ratingB += currentList.get(j).getRating();
		}
		return Math.abs(ratingA - ratingB);
	}

	@Override
	public void createMatch(List<Player> teamA, List<Player> teamB) {
		setMatch(new Match(teamA, teamB));
	}

	@Override
	public void simulateMatch() {
		// calculate team A chance of winning based on skill
		// base 50% = 500
		// skill of A = 100 points of skill diff = 100% chance of winning
		int chanceToWinOfTeamA = 500 + (getMatch().getTeamAskill() - getMatch().getTeamBskill()) * 5;
		getMatch().setChanceToWinOfTeamAInPercent((int) chanceToWinOfTeamA / 10);

		if (getMatch().getChanceToWinOfTeamAInPercent() > 100)
			getMatch().setChanceToWinOfTeamAInPercent(100);
		if (getMatch().getChanceToWinOfTeamAInPercent() < 0)
			getMatch().setChanceToWinOfTeamAInPercent(0);
		// roll a dice
		Random rndRandom = new Random();
		if (rndRandom.nextInt(1000) < chanceToWinOfTeamA) {
			getMatch().setTeamAwins(true);
		} else
			getMatch().setTeamAwins(false);
		// calculate winning team's rating boost based on player rating -
		// opposing team rating
		int ratingDiff = getMatch().getTeamArating() - getMatch().getTeamBrating();
		boolean teamAisLikelyToWin = false;
		boolean equalChances = true;
		if (ratingDiff < 0) {
			equalChances = false;
			teamAisLikelyToWin = false;
		} else if (ratingDiff > 0) {
			equalChances = false;
			teamAisLikelyToWin = true;
		}

		int chanceToWinOfTeamABasedOnRating = 500 + (getMatch().getTeamArating() - getMatch().getTeamBrating()) / 5;
		getMatch().setChanceToWinOfTeamABasedOnRatingPercent(chanceToWinOfTeamABasedOnRating / 10);
		if (equalChances) {
			// rating +/- 20
			if (getMatch().getTeamAwins())
				for (int i = 0; i < getMatch().getTeamA().size(); i++) {
					getMatch().getTeamA().get(i).setLastRatingChange(20);
					getMatch().getTeamB().get(i).setLastRatingChange(-20);
				}
			else {
				for (int i = 0; i < getMatch().getTeamB().size(); i++) {
					getMatch().getTeamA().get(i).setLastRatingChange(-20);
					getMatch().getTeamB().get(i).setLastRatingChange(20);
				}
			}
		} else if (teamAisLikelyToWin & getMatch().getTeamAwins()) {
			// team A gets with chance 52% = (1-0.52)*1.8*20
			// team B gets with chance 48% = -(0.52)*2.3*20
			for (int i = 0; i < getMatch().getTeamA().size(); i++) {
				getMatch().getTeamA().get(i)
						.setLastRatingChange((int) Math.floor((double) (chanceToWinOfTeamABasedOnRating) / 1000 * 1.8 * 20));
				getMatch().getTeamB().get(i)
						.setLastRatingChange((int) Math.floor((double) (chanceToWinOfTeamABasedOnRating) / 1000 * 1.8 * (-20)));
			}
		} else if (!teamAisLikelyToWin & getMatch().getTeamAwins()) {
			// team A gets with chance 48% = (1-0.48)*2.3*20
			// team B gets with chance 52% = -(0.48)*1.8*20
			for (int i = 0; i < getMatch().getTeamA().size(); i++) {
				getMatch().getTeamA().get(i)
						.setLastRatingChange((int) Math.floor((double) (chanceToWinOfTeamABasedOnRating) / 1000 * 2.3 * 20));
				getMatch().getTeamB().get(i)
						.setLastRatingChange((int) Math.floor((double) (chanceToWinOfTeamABasedOnRating) / 1000 * 2.3 * (-20)));
			}
		} else if (teamAisLikelyToWin & !getMatch().getTeamAwins()) {
			// team B gets with chance 48% = (0.52)*2.3*20
			// team A gets with chance 52% = -(1-0.52)*1.8*20
			for (int i = 0; i < getMatch().getTeamA().size(); i++) {
				getMatch().getTeamB().get(i)
						.setLastRatingChange((int) Math.floor((double) (chanceToWinOfTeamABasedOnRating) / 1000 * 2.3 * 20));
				getMatch().getTeamA().get(i)
						.setLastRatingChange((int) Math.floor((double) (chanceToWinOfTeamABasedOnRating) / 1000 * 2.3 * (-20)));
			}
		} else if (!teamAisLikelyToWin & !getMatch().getTeamAwins()) {
			// team B gets with chance 52% = (0.48)*1.8*20
			// team A gets with chance 48% = -(1-0.48)*2.3*20
			for (int i = 0; i < getMatch().getTeamA().size(); i++) {
				getMatch().getTeamB().get(i)
						.setLastRatingChange((int) Math.floor((double) (chanceToWinOfTeamABasedOnRating) / 1000 * 1.8 * 20));
				getMatch().getTeamA().get(i)
						.setLastRatingChange((int) Math.floor((double) (chanceToWinOfTeamABasedOnRating) / 1000 * 1.8 * (-20)));
			}
		}

		// wins/losses and streak rating increase
		if (getMatch().getTeamAwins()) {
			for (int i = 0; i < getMatch().getTeamA().size(); i++) {

				getMatch().getTeamA().get(i).setWins(getMatch().getTeamA().get(i).getWins() + 1);

				if (getMatch().getTeamA().get(i).getWinsInARow() <= 0)
					getMatch().getTeamA().get(i).setWinsInARow(1);
				else
					getMatch().getTeamA().get(i).setWinsInARow(getMatch().getTeamA().get(i).getWinsInARow() + 1);

				if (getMatch().getTeamB().get(i).getWinsInARow() >= 0)
					getMatch().getTeamB().get(i).setWinsInARow(-1);
				else
					getMatch().getTeamB().get(i).setWinsInARow(getMatch().getTeamB().get(i).getWinsInARow() - 1);

				getMatch()
						.getTeamA()
						.get(i)
						.setLastRatingChange(
								(int) (getMatch().getTeamA().get(i).getLastRatingChange() * getMatch().getTeamA().get(i).getWinsInARow() * 0.8));
				getMatch()
						.getTeamB()
						.get(i)
						.setLastRatingChange(
								(int) (getMatch().getTeamB().get(i).getLastRatingChange() * (-getMatch().getTeamB().get(i).getWinsInARow() * 0.8)));
			}
		}
		if (!getMatch().getTeamAwins()) {
			for (int i = 0; i < getMatch().getTeamB().size(); i++) {

				getMatch().getTeamB().get(i).setWins(getMatch().getTeamB().get(i).getWins() + 1);

				if (getMatch().getTeamB().get(i).getWinsInARow() <= 0)
					getMatch().getTeamB().get(i).setWinsInARow(1);
				else
					getMatch().getTeamB().get(i).setWinsInARow(getMatch().getTeamB().get(i).getWinsInARow() + 1);

				if (getMatch().getTeamA().get(i).getWinsInARow() >= 0)
					getMatch().getTeamA().get(i).setWinsInARow(-1);
				else
					getMatch().getTeamA().get(i).setWinsInARow(getMatch().getTeamA().get(i).getWinsInARow() - 1);

				getMatch()
						.getTeamB()
						.get(i)
						.setLastRatingChange(
								(int) (getMatch().getTeamB().get(i).getLastRatingChange() * getMatch().getTeamB().get(i).getWinsInARow() * 0.8));
				getMatch()
						.getTeamA()
						.get(i)
						.setLastRatingChange(
								(int) (getMatch().getTeamA().get(i).getLastRatingChange() * (-getMatch().getTeamA().get(i).getWinsInARow() * 0.8)));

			}
		}

		// placement matches + change rating + games played++
		for (int i = 0; i < getMatch().getTeamA().size(); i++) {
			if (getMatch().getTeamA().get(i).getPlacementLeft() > 0) {
				getMatch().getTeamA().get(i).setLastRatingChange(getMatch().getTeamA().get(i).getLastRatingChange() * 10);
				getMatch().getTeamA().get(i).setPlacementLeft(getMatch().getTeamA().get(i).getPlacementLeft() - 1);
			}
			getMatch().getTeamA().get(i)
					.setRating(getMatch().getTeamA().get(i).getRating() + getMatch().getTeamA().get(i).getLastRatingChange());

			if (getMatch().getTeamB().get(i).getPlacementLeft() > 0) {
				getMatch().getTeamB().get(i).setLastRatingChange(getMatch().getTeamB().get(i).getLastRatingChange() * 10);
				getMatch().getTeamB().get(i).setPlacementLeft(getMatch().getTeamB().get(i).getPlacementLeft() - 1);
			}
			getMatch().getTeamB().get(i)
					.setRating(getMatch().getTeamB().get(i).getRating() + getMatch().getTeamB().get(i).getLastRatingChange());

			getMatch().getTeamA().get(i).setGamesPlayed(getMatch().getTeamA().get(i).getGamesPlayed() + 1);
			getMatch().getTeamB().get(i).setGamesPlayed(getMatch().getTeamB().get(i).getGamesPlayed() + 1);

		}

	}

	@Override
	public void findMatchForSelectedPlayer(Player selected_player, int pool_size) {
		Random rnd = new Random();

		List<Player> dataBaseList = playerBaseManager.getListOfPlayers();
		List<Player> playerPoolList = new ArrayList<Player>();
		for (int i = 0; i < pool_size; i++) {
			playerPoolList.add(dataBaseList.get(rnd.nextInt(dataBaseList.size())));
		}
		playerPoolList.add(playerBaseManager.getById(selected_player.getId()));

		createMatchFinder(playerPoolList, playerBaseManager.getById(selected_player.getId()), 50, 50);

		setMatch(findMatch());

	}

	@Override
	public void saveResult() {
		List<Player> updList = new ArrayList<Player>(getMatch().getTeamB());
		playerBaseManager.massUpdate(updList);

		updList = new ArrayList<Player>(getMatch().getTeamA());
		playerBaseManager.massUpdate(updList);
	}

	public MatchFinder getMF() {
		return MF;
	}

	public void setMF(MatchFinder mF) {
		MF = mF;
	}

	@Override
	public Match getMatch() {
		return match;
	}

	public void setMatch(Match match) {
		this.match = match;
	}
}
